home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / var / lib / python-support / python2.6 / cupshelpers / ppds.pyc (.txt) < prev   
Encoding:
Python Compiled Bytecode  |  2009-10-12  |  28.7 KB  |  1,018 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. import cups
  5. from cupshelpers import parseDeviceID
  6. import string
  7. import locale
  8. import os.path as os
  9. import re
  10. from  import _debugprint
  11. __all__ = [
  12.     'ppdMakeModelSplit',
  13.     'PPDs']
  14.  
  15. def ppdMakeModelSplit(ppd_make_and_model):
  16.     '''
  17.     Split a ppd-make-and-model string into a canonical make and model pair.
  18.  
  19.     @type ppd_make_and_model: string
  20.     @param ppd_make_and_model: IPP ppd-make-and-model attribute
  21.     @return: a string pair representing the make and the model
  22.     '''
  23.     if re.search('^\\s*(deskjet|dj\x08|dj\\d|laserjet|lj\x08|color\\s*laserjet|color\\s*lj\x08|designjet|officejet|oj\x08|photosmart|ps\x08|psc)', ppd_make_and_model, re.I) or re.search('(edgeline)', ppd_make_and_model, re.I):
  24.         make = 'HP'
  25.         model = ppd_make_and_model
  26.     elif re.search('^\\s*(stylus|aculaser)', ppd_make_and_model, re.I):
  27.         make = 'Epson'
  28.         model = ppd_make_and_model
  29.     elif re.search('^\\s*(stylewriter|imagewriter|deskwriter|laserwriter)', ppd_make_and_model, re.I):
  30.         make = 'Apple'
  31.         model = ppd_make_and_model
  32.     elif re.search('^\\s*(pixus|pixma|selphy|imagerunner|\x08bjc\x08|\x08bj\x08|\x08lbp\x08)', ppd_make_and_model, re.I):
  33.         make = 'Canon'
  34.         model = ppd_make_and_model
  35.     elif re.search('^\\s*(\x08hl\x08|\x08dcp\x08|\x08mfc\x08)', ppd_make_and_model, re.I):
  36.         make = 'Brother'
  37.         model = ppd_make_and_model
  38.     elif re.search('^\\s*(docuprint|docupage|phaser|workcentre|homecentre)', ppd_make_and_model, re.I):
  39.         make = 'Xerox'
  40.         model = ppd_make_and_model
  41.     elif re.search('^\\s*(optra|(color\\s*|)jetprinter)', ppd_make_and_model, re.I):
  42.         make = 'Lexmark'
  43.         model = ppd_make_and_model
  44.     elif re.search('^\\s*(magicolor|pageworks|pagepro)', ppd_make_and_model, re.I):
  45.         make = 'KONICA MINOLTA'
  46.         model = ppd_make_and_model
  47.     elif re.search('^\\s*(aficio)', ppd_make_and_model, re.I):
  48.         make = 'Ricoh'
  49.         model = ppd_make_and_model
  50.     elif re.search('^\\s*(varioprint)', ppd_make_and_model, re.I):
  51.         make = 'Oce'
  52.         model = ppd_make_and_model
  53.     elif re.search('^\\s*(okipage|microline)', ppd_make_and_model, re.I):
  54.         make = 'Oki'
  55.         model = ppd_make_and_model
  56.     elif re.search('^\\s*(konica[\\s_-]*minolta)', ppd_make_and_model, re.I):
  57.         make = 'KONICA MINOLTA'
  58.         model = ppd_make_and_model
  59.         model = re.sub('(?i)KONICA[\\s_-]*MINOLTA\\s*', '', model, 1)
  60.     elif re.search('turboprint', ppd_make_and_model, re.I):
  61.         t = ppd_make_and_model.find(' TurboPrint')
  62.         if t != -1:
  63.             ppd_make_and_model = ppd_make_and_model[:t]
  64.         
  65.         
  66.         try:
  67.             (make, model) = ppd_make_and_model.split('_', 1)
  68.         except:
  69.             make = ppd_make_and_model
  70.             model = ''
  71.  
  72.         make = re.sub('(?<=[a-z])(?=[0-9])', ' ', make)
  73.         make = re.sub('(?<=[a-z])(?=[A-Z])', ' ', make)
  74.         model = re.sub('(?<=[a-z])(?=[0-9])', ' ', model)
  75.         model = re.sub('(?<=[a-z])(?=[A-Z])', ' ', model)
  76.         model = re.sub(' Jet', 'Jet', model)
  77.         model = re.sub('Photo Smart', 'PhotoSmart', model)
  78.     else:
  79.         
  80.         try:
  81.             (make, model) = ppd_make_and_model.split(' ', 1)
  82.         except:
  83.             make = ppd_make_and_model
  84.             model = ''
  85.  
  86.     
  87.     def strip_suffix(model, suffix):
  88.         if model.endswith(suffix):
  89.             return model[:-len(suffix)]
  90.         return model
  91.  
  92.     c = model.find(',')
  93.     if c != -1:
  94.         model = model[:c]
  95.     
  96.     v = model.find(' v')
  97.     if v != -1:
  98.         if (model[v + 2].isdigit() or model[v + 2] == '.') and model[v + 3].isdigit():
  99.             model = model[:v]
  100.         
  101.     h = model.find(' hpijs')
  102.     if h != -1:
  103.         model = model[:h]
  104.     
  105.     f = model.find(' Foomatic/')
  106.     if f != -1:
  107.         model = model[:f]
  108.     
  109.     gutenprint = model.find(' - CUPS+Gutenprint')
  110.     if gutenprint != -1:
  111.         model = model[:gutenprint]
  112.     
  113.     gimpprint = model.find(' - CUPS+Gimp-Print')
  114.     if gimpprint != -1:
  115.         model = model[:gimpprint]
  116.     
  117.     wth = model.find(' w/')
  118.     if wth != -1:
  119.         model = model[:wth]
  120.     
  121.     make = re.sub('(?i)KONICA[\\s_-]*MINOLTA', 'KONICA MINOLTA', make, 1)
  122.     make = re.sub('(?i)HEWLETT[\\s_-]PACKARD', 'HP', make, 1)
  123.     make = re.sub('(?i)Lexmark\\s*International', 'Lexmark', make, 1)
  124.     model = re.sub('(?i)\\s*\\(recommended\\)', '', model)
  125.     model = re.sub('(?i)\\s*-\\s*PostScript\\b', '', model)
  126.     model = re.sub('(?i)\\s*\\bseries\\b', '', model)
  127.     if make.lower() == 'hp':
  128.         model = re.sub('(?i)^dj\\b', 'DeskJet', model)
  129.         model = re.sub('(?i)^LJ\\b', 'LaserJet', model)
  130.         model = re.sub('(?i)^OJ\\b', 'OfficeJet', model)
  131.         model = re.sub('(?i)^Color\\s*LJ\\b', 'Color LaserJet', model)
  132.         model = re.sub('(?i)^PS\\b', 'PhotoSmart', model)
  133.     
  134.     model = re.sub('(?i)\\s*\\bPS[123]?\\b', '', model)
  135.     model = re.sub('(?i)\\s*\\bPXL', '', model)
  136.     model = re.sub('(?i)[\\s_-]+BT\\b', '', model)
  137.     model = re.sub('(?i)\\s*\\(Bluetooth\\)', '', model)
  138.     model = re.sub('(?i)\\s*-\\s*(RC|Ver(|sion))\\s*-*\\s*[0-9\\.]+', '', model)
  139.     model = re.sub('(?i)\\s*-\\s*(RC|Ver(|sion))\\b', '', model)
  140.     model = re.sub('(?i)\\s*PostScript\\s*$', '', model)
  141.     model = re.sub('(?i)\\s*\\(\\s*\\)', '', model)
  142.     model = re.sub('(?i)\\s*[\\-\\/]\\s*$', '', model)
  143.     for mfr in [
  144.         'Apple',
  145.         'Canon',
  146.         'Epson',
  147.         'Lexmark',
  148.         'Oki']:
  149.         if make == mfr.upper():
  150.             make = mfr
  151.             continue
  152.     
  153.     model = model.strip()
  154.     return (make, model)
  155.  
  156. DRIVER_TYPE_DOWNLOADED_NOW = 5
  157. DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT = 8
  158. DRIVER_TYPE_VENDOR = 10
  159. DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT = 15
  160. DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP = 17
  161. DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED = 20
  162. DRIVER_TYPE_GUTENPRINT_NATIVE = 25
  163. DRIVER_TYPE_SPLIX = 27
  164. DRIVER_TYPE_FOOMATIC_PS = 30
  165. DRIVER_TYPE_FOOMATIC_HPIJS = 40
  166. DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED = 50
  167. DRIVER_TYPE_FOOMATIC_GUTENPRINT = 60
  168. DRIVER_TYPE_FOOMATIC = 70
  169. DRIVER_TYPE_CUPS = 80
  170. DRIVER_TYPE_FOOMATIC_GENERIC = 90
  171. DRIVER_TYPE_3RD_PARTY_NONFREE = 95
  172. DRIVER_DOES_NOT_WORK = 999
  173.  
  174. def _getDriverType(ppdname, ppds = None):
  175.     '''Decides which of the above types ppdname is.'''
  176.     if ppdname.find('turboprint') != -1:
  177.         return DRIVER_TYPE_3RD_PARTY_NONFREE
  178.     if ppdname.find('gutenprint') != -1:
  179.         if ppdname.find('/simple/') != -1 or ppdname.find('.sim-') != -1:
  180.             return DRIVER_TYPE_GUTENPRINT_NATIVE_SIMPLIFIED
  181.         return DRIVER_TYPE_GUTENPRINT_NATIVE
  182.     ppdname.find('gutenprint') != -1
  183.     if ppdname.find('splix') != -1:
  184.         return DRIVER_TYPE_SPLIX
  185.     if ppdname.find(':') == -1 and ppdname.find('/cups-included/') != -1:
  186.         return DRIVER_TYPE_CUPS
  187.     if ppdname.startswith('foomatic:'):
  188.         if ppdname.find('Generic') != -1:
  189.             return DRIVER_TYPE_FOOMATIC_GENERIC
  190.         if ppds != None and ppds.getInfoFromPPDName(ppdname).get('ppd-make-and-model', '').find('(recommended)') != -1:
  191.             if ppds.getInfoFromPPDName(ppdname).get('ppd-make-and-model', '').find('Postscript') != -1:
  192.                 return DRIVER_TYPE_FOOMATIC_RECOMMENDED_POSTSCRIPT
  193.             return DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  194.         ppds.getInfoFromPPDName(ppdname).get('ppd-make-and-model', '').find('(recommended)') != -1
  195.         if ppdname.find('-Postscript') != -1:
  196.             return DRIVER_TYPE_FOOMATIC_PS
  197.         if ppdname.find('-gutenprint') != -1:
  198.             if ppdname.find('-simplified') != -1:
  199.                 return DRIVER_TYPE_FOOMATIC_GUTENPRINT_SIMPLIFIED
  200.             return DRIVER_TYPE_FOOMATIC_GUTENPRINT
  201.         return DRIVER_TYPE_FOOMATIC
  202.     return DRIVER_TYPE_VENDOR
  203.  
  204.  
  205. class PPDs:
  206.     '''
  207.     This class is for handling the list of PPDs returned by CUPS.  It
  208.     indexes by PPD name and device ID, filters by natural language so
  209.     that foreign-language PPDs are not included, and sorts by driver
  210.     type.  If an exactly-matching PPD is not available, it can
  211.     substitute with a PPD for a similar model or for a generic driver.
  212.     '''
  213.     STATUS_SUCCESS = 0
  214.     STATUS_MODEL_MISMATCH = 1
  215.     STATUS_GENERIC_DRIVER = 2
  216.     STATUS_NO_DRIVER = 3
  217.     
  218.     def __init__(self, ppds, language = None):
  219.         '''
  220.         @type ppds: dict
  221.         @param ppds: dict of PPDs as returned by cups.Connection.getPPDs()
  222.  
  223.         @type language: string
  224. \t@param language: language name, as given by the first element
  225.         of the pair returned by locale.getlocale()
  226.         '''
  227.         self.ppds = ppds.copy()
  228.         self.makes = None
  229.         self.ids = None
  230.         if language == None and language == 'C' or language == 'POSIX':
  231.             language = 'en_US'
  232.         
  233.         u = language.find('_')
  234.         if u != -1:
  235.             short_language = language[:u]
  236.         else:
  237.             short_language = language
  238.         to_remove = []
  239.         for ppdname, ppddict in self.ppds.iteritems():
  240.             
  241.             try:
  242.                 natural_language = ppddict['ppd-natural-language']
  243.             except KeyError:
  244.                 continue
  245.  
  246.             if natural_language == 'en':
  247.                 continue
  248.             
  249.             if natural_language == language:
  250.                 continue
  251.             
  252.             if natural_language == short_language:
  253.                 continue
  254.             
  255.             to_remove.append(ppdname)
  256.         
  257.         for ppdname in to_remove:
  258.             del self.ppds[ppdname]
  259.         
  260.         if self.ppds.has_key('raw'):
  261.             makemodel = self.ppds['raw']['ppd-make-and-model']
  262.             if not makemodel.startswith('Generic '):
  263.                 self.ppds['raw']['ppd-make-and-model'] = 'Generic ' + makemodel
  264.             
  265.         
  266.  
  267.     
  268.     def getMakes(self):
  269.         '''
  270. \t@returns: a list of strings representing makes, sorted according
  271.         to the current locale
  272. \t'''
  273.         self._init_makes()
  274.         makes_list = self.makes.keys()
  275.         makes_list.sort(locale.strcoll)
  276.         
  277.         try:
  278.             makes_list.remove('Generic')
  279.             makes_list.insert(0, 'Generic')
  280.         except ValueError:
  281.             pass
  282.  
  283.         return makes_list
  284.  
  285.     
  286.     def getModels(self, make):
  287.         '''
  288. \t@returns: a list of strings representing models, sorted using
  289. \tcups.modelSort()
  290. \t'''
  291.         self._init_makes()
  292.         
  293.         try:
  294.             models_list = self.makes[make].keys()
  295.         except KeyError:
  296.             return []
  297.  
  298.         models_list.sort(key = unicode.lower, cmp = cups.modelSort)
  299.         return models_list
  300.  
  301.     
  302.     def getInfoFromModel(self, make, model):
  303.         '''
  304. \tObtain a list of PPDs that are suitable for use with a
  305.         particular printer model, given its make and model name.
  306.  
  307. \t@returns: a dict, indexed by ppd-name, of dicts representing
  308.         PPDs (as given by cups.Connection.getPPDs)
  309. \t'''
  310.         self._init_makes()
  311.         
  312.         try:
  313.             return self.makes[make][model]
  314.         except KeyError:
  315.             return { }
  316.  
  317.  
  318.     
  319.     def getInfoFromPPDName(self, ppdname):
  320.         '''
  321. \t@returns: a dict representing a PPD, as given by
  322. \tcups.Connection.getPPDs
  323. \t'''
  324.         return self.ppds[ppdname]
  325.  
  326.     
  327.     def orderPPDNamesByPreference(self, ppdnamelist = [], downloadedfiles = []):
  328.         '''
  329.  
  330. \tSort a list of PPD names by (hard-coded) preferred driver
  331. \ttype.
  332.  
  333. \t@param ppdnamelist: PPD names
  334. \t@type ppdnamelist: string list
  335. \t@returns: string list
  336. \t'''
  337.         if len(ppdnamelist) < 1:
  338.             return ppdnamelist
  339.         dict = self.getInfoFromPPDName(ppdnamelist[0])
  340.         make_model = dict['ppd-make-and-model']
  341.         (mfg, mdl) = ppdMakeModelSplit(make_model)
  342.         
  343.         def getDriverTypeWithBias(x, mfg):
  344.             t = _getDriverType(x, ppds = self)
  345.             for file in downloadedfiles:
  346.                 (path, slash, filename) = file.rpartition('/')
  347.                 (xpath, xslash, xfilename) = x.rpartition('/')
  348.                 if filename == xfilename:
  349.                     return DRIVER_TYPE_DOWNLOADED_NOW
  350.             
  351.             if mfg == 'HP':
  352.                 if t == DRIVER_TYPE_FOOMATIC_HPIJS:
  353.                     t = DRIVER_TYPE_FOOMATIC_HPIJS_ON_HP
  354.                     if re.search('(?i)HP[-_]LaserJet_1[23]\\d\\d', x):
  355.                         t = DRIVER_TYPE_FOOMATIC_RECOMMENDED_NON_POSTSCRIPT
  356.                     
  357.                 
  358.             
  359.             return t
  360.  
  361.         
  362.         def sort_ppdnames(a, b):
  363.             ta = getDriverTypeWithBias(a, mfg)
  364.             tb = getDriverTypeWithBias(b, mfg)
  365.             if ta != tb:
  366.                 if tb < ta:
  367.                     return 1
  368.                 return -1
  369.             ta != tb
  370.             
  371.             def is_C_locale(x):
  372.                 
  373.                 try:
  374.                     while x:
  375.                         i = x.find('C')
  376.                         if i == -1:
  377.                             return False
  378.                         lword = False
  379.                         if i == 0:
  380.                             lword = True
  381.                         elif x[i - 1] not in string.letters:
  382.                             lword = True
  383.                         
  384.                         if lword:
  385.                             rword = False
  386.                             if i == len(x) - 1:
  387.                                 rword = True
  388.                             elif x[i + 1] not in string.letters:
  389.                                 rword = True
  390.                             
  391.                             if rword:
  392.                                 return True
  393.                         
  394.                         x = x[i + 1:]
  395.                 except UnicodeDecodeError:
  396.                     return False
  397.  
  398.  
  399.             ca = is_C_locale(a)
  400.             cb = is_C_locale(b)
  401.             if ca != cb:
  402.                 if ca:
  403.                     l = a.find('C')
  404.                 else:
  405.                     l = b.find('C')
  406.                 if a[:l] == b[:l]:
  407.                     if cb:
  408.                         return 1
  409.                     return -1
  410.                 a[:l] == b[:l]
  411.             
  412.             if a > b:
  413.                 return 1
  414.             if a < b:
  415.                 return -1
  416.             return 0
  417.  
  418.         ppdnamelist.sort(sort_ppdnames)
  419.         return ppdnamelist
  420.  
  421.     
  422.     def getPPDNameFromDeviceID(self, mfg, mdl, description = '', commandsets = [], uri = None, downloadedfiles = []):
  423.         '''
  424. \tObtain a best-effort PPD match for an IEEE 1284 Device ID.
  425. \tThe status is one of:
  426.  
  427. \t  - L{STATUS_SUCCESS}: the match was successful, and an exact
  428.             match was found
  429.  
  430. \t  - L{STATUS_MODEL_MISMATCH}: a similar match was found, but
  431.             the model name does not exactly match
  432.  
  433. \t  - L{STATUS_GENERIC_DRIVER}: no match was found, but a
  434.             generic driver is available that can drive this device
  435.             according to its command set list
  436.  
  437. \t  - L{STATUS_NO_DRIVER}: no match was found at all, and the
  438.             returned PPD name is a last resort
  439.  
  440. \t@param mfg: MFG or MANUFACTURER field
  441. \t@type mfg: string
  442. \t@param mdl: MDL or MODEL field
  443. \t@type mdl: string
  444. \t@param description: DES or DESCRIPTION field, optional
  445. \t@type description: string
  446. \t@param commandsets: CMD or COMMANDSET field, optional
  447. \t@type commandsets: string
  448. \t@param uri: device URI, optional (only needed for debugging)
  449. \t@type uri: string
  450. \t@returns: an integer,string pair of (status,ppd-name)
  451. \t'''
  452.         _debugprint('\n%s %s' % (mfg, mdl))
  453.         self._init_ids()
  454.         if mfg.lower() == 'hewlett-packard':
  455.             mfg = 'HP'
  456.         
  457.         ppdnamelist = []
  458.         id_matched = False
  459.         
  460.         try:
  461.             ppdnamelist = self.ids[mfg.lower()][mdl.lower()]
  462.             status = self.STATUS_SUCCESS
  463.             id_matched = True
  464.         except KeyError:
  465.             pass
  466.  
  467.         if mfg.lower() == 'hp':
  468.             
  469.             try:
  470.                 ppdnamelist += self.ids['hp'][mdl.lower()]
  471.                 status = self.STATUS_SUCCESS
  472.                 id_matched = True
  473.             except KeyError:
  474.                 pass
  475.  
  476.             
  477.             try:
  478.                 ppdnamelist += self.ids['hp'][mdl.lower().replace(' ', '_')]
  479.                 status = self.STATUS_SUCCESS
  480.                 id_matched = True
  481.             except KeyError:
  482.                 pass
  483.             except:
  484.                 None<EXCEPTION MATCH>KeyError
  485.             
  486.  
  487.         None<EXCEPTION MATCH>KeyError
  488.         if mfg.lower() == 'apollo':
  489.             
  490.             try:
  491.                 ppdnamelist += self.ids['apollo'][mdl.lower()]
  492.                 status = self.STATUS_SUCCESS
  493.                 id_matched = True
  494.             except KeyError:
  495.                 pass
  496.  
  497.             
  498.             try:
  499.                 ppdnamelist += self.ids['apollo'][mdl.lower().replace(' ', '_')]
  500.                 status = self.STATUS_SUCCESS
  501.                 id_matched = True
  502.             except KeyError:
  503.                 pass
  504.             except:
  505.                 None<EXCEPTION MATCH>KeyError
  506.             
  507.  
  508.         None<EXCEPTION MATCH>KeyError
  509.         _debugprint('Trying make/model names')
  510.         mfgl = mfg.lower()
  511.         mdls = None
  512.         self._init_makes()
  513.         for attempt in range(2):
  514.             for make, models in self.makes.iteritems():
  515.                 if make.lower() == mfgl:
  516.                     mdls = models
  517.                     break
  518.                     continue
  519.             
  520.             if mfgl == 'hewlett-packard':
  521.                 mfgl = 'hp'
  522.                 continue
  523.             if mfgl == 'lexmark international':
  524.                 mfgl = 'lexmark'
  525.                 continue
  526.             if mfgl == '':
  527.                 (tmp1, tmp2) = ppdMakeModelSplit(mdl)
  528.                 mfgl = tmp1.lower()
  529.                 continue
  530.         
  531.         ppdnamelist2 = None
  532.         if mdl.startswith(mfg + ' '):
  533.             mdl = mdl[len(mfg) + 1:]
  534.         
  535.         if mdl.startswith('Hewlett-Packard '):
  536.             mdl = mdl[16:]
  537.         
  538.         if mdl.startswith('HP '):
  539.             mdl = mdl[3:]
  540.         
  541.         if mdls:
  542.             for model, ppdnames in mdls.iteritems():
  543.                 if model.lower() == mdl.lower():
  544.                     ppdnamelist2 = ppdnames.keys()
  545.                     status = self.STATUS_SUCCESS
  546.                     break
  547.                     continue
  548.             
  549.             if not ppdnamelist2:
  550.                 (mfg2, mdl2) = ppdMakeModelSplit(mfg + ' ' + mdl)
  551.                 for model, ppdnames in mdls.iteritems():
  552.                     if model.lower() == mdl2.lower():
  553.                         ppdnamelist2 = ppdnames.keys()
  554.                         status = self.STATUS_SUCCESS
  555.                         break
  556.                         continue
  557.                 
  558.             
  559.         
  560.         if ppdnamelist:
  561.             if ppdnamelist2:
  562.                 ppdnamelist = ppdnamelist + ppdnamelist2
  563.             
  564.         elif ppdnamelist2:
  565.             ppdnamelist = ppdnamelist2
  566.         
  567.         if not ppdnamelist and mdls:
  568.             (s, ppds) = self._findBestMatchPPDs(mdls, mdl)
  569.             if s != self.STATUS_NO_DRIVER:
  570.                 status = s
  571.                 ppdnamelist = ppds
  572.             
  573.         
  574.         if not ppdnamelist and commandsets:
  575.             if type(commandsets) != list:
  576.                 commandsets = commandsets.split(',')
  577.             
  578.             generic = self._getPPDNameFromCommandSet(commandsets)
  579.             if generic:
  580.                 status = self.STATUS_GENERIC_DRIVER
  581.                 ppdnamelist = generic
  582.             
  583.         
  584.         if not ppdnamelist:
  585.             _debugprint('Text-only fallback')
  586.             status = self.STATUS_NO_DRIVER
  587.             ppdnamelist = [
  588.                 'textonly.ppd']
  589.             tppdfound = 0
  590.             for ppdpath in self.ppds.keys():
  591.                 if ppdpath.endswith(ppdnamelist[0]):
  592.                     tppdfound = 1
  593.                     ppdnamelist = [
  594.                         ppdpath]
  595.                     break
  596.                     continue
  597.             
  598.             if tppdfound == 0:
  599.                 _debugprint('No text-only driver?!  Using postscript.ppd')
  600.                 ppdnamelist = [
  601.                     'postscript.ppd']
  602.                 psppdfound = 0
  603.                 for ppdpath in self.ppds.keys():
  604.                     if ppdpath.endswith(ppdnamelist[0]):
  605.                         psppdfound = 1
  606.                         ppdnamelist = [
  607.                             ppdpath]
  608.                         break
  609.                         continue
  610.                 
  611.                 if psppdfound == 0:
  612.                     _debugprint('No postscript.ppd; choosing any')
  613.                     ppdnamelist = [
  614.                         self.ppds.keys()[0]]
  615.                 
  616.             
  617.         
  618.         if id_matched:
  619.             _debugprint('Checking DES field')
  620.             inexact = set()
  621.             if description:
  622.                 for ppdname in ppdnamelist:
  623.                     if ppdname.find('hpijs'):
  624.                         continue
  625.                     
  626.                     ppddict = self.ppds[ppdname]
  627.                     id = ppddict['ppd-device-id']
  628.                     if not id:
  629.                         continue
  630.                     
  631.                     id_dict = parseDeviceID(id)
  632.                     if id_dict['DES'] != description:
  633.                         inexact.add(ppdname)
  634.                         continue
  635.                 
  636.             
  637.             exact = set(ppdnamelist).difference(inexact)
  638.             _debugprint('discarding: %s' % inexact)
  639.             if len(exact) >= 1:
  640.                 ppdnamelist = list(exact)
  641.             
  642.         
  643.         ppdnamelist = self.orderPPDNamesByPreference(ppdnamelist, downloadedfiles)
  644.         _debugprint('Found PPDs: %s' % str(ppdnamelist))
  645.         if not id_matched:
  646.             sanitised_uri = re.sub(pattern = '//[^@]*@/?', repl = '//', string = str(uri))
  647.             print 'No ID match for device %s:' % sanitised_uri
  648.             print '  <manufacturer>%s</manufacturer>' % mfg
  649.             print '  <model>%s</model>' % mdl
  650.             print '  <description>%s</description>' % description
  651.             
  652.             try:
  653.                 cmd = reduce((lambda x, y: x + ',' + y), commandsets)
  654.             except TypeError:
  655.                 cmd = ''
  656.  
  657.             print '  <commandset>%s</commandset>' % cmd
  658.             print 'Using %s' % ppdnamelist[0]
  659.         
  660.         return (status, ppdnamelist[0])
  661.  
  662.     
  663.     def _findBestMatchPPDs(self, mdls, mdl):
  664.         '''
  665.         Find the best-matching PPDs based on the MDL Device ID.
  666.         This function could be made a lot smarter.
  667.         '''
  668.         _debugprint('Trying best match')
  669.         mdll = mdl.lower()
  670.         if mdll.endswith(' series'):
  671.             mdll = mdll[:-7]
  672.             mdl = mdl[:-7]
  673.         
  674.         best_mdl = None
  675.         best_matchlen = 0
  676.         mdlnames = mdls.keys()
  677.         mdlnamesl = map((lambda x: (x, x.lower())), mdlnames)
  678.         mdlnamesl.append((mdl, mdll))
  679.         mdlnamesl.sort((lambda x, y: cups.modelSort(x[1], y[1])))
  680.         i = mdlnamesl.index((mdl, mdll))
  681.         candidates = [
  682.             mdlnamesl[i - 1]]
  683.         if i + 1 < len(mdlnamesl):
  684.             candidates.append(mdlnamesl[i + 1])
  685.             _debugprint(candidates[0][0] + ' <= ' + mdl + ' <= ' + candidates[1][0])
  686.         else:
  687.             _debugprint(candidates[0][0] + ' <= ' + mdl)
  688.         for candidate, candidatel in candidates:
  689.             prefix = os.path.commonprefix([
  690.                 candidatel,
  691.                 mdll])
  692.             if len(prefix) > best_matchlen:
  693.                 best_mdl = mdls[candidate].keys()
  694.                 best_matchlen = len(prefix)
  695.                 _debugprint('%s: match length %d' % (candidate, best_matchlen))
  696.                 continue
  697.         
  698.         if best_mdl and best_matchlen > len(mdll) / 2:
  699.             ppdnamelist = best_mdl
  700.             if best_matchlen == len(mdll):
  701.                 status = self.STATUS_SUCCESS
  702.             else:
  703.                 status = self.STATUS_MODEL_MISMATCH
  704.         else:
  705.             status = self.STATUS_NO_DRIVER
  706.             ppdnamelist = None
  707.             mdlnames.sort(cups.modelSort)
  708.             mdlitems = (map,)((lambda x: (x.lower(), mdls[x])), mdlnames)
  709.             modelid = None
  710.             for word in mdll.split(' '):
  711.                 if modelid == None:
  712.                     modelid = word
  713.                 
  714.                 have_digits = False
  715.                 for i in range(len(word)):
  716.                     if word[i].isdigit():
  717.                         have_digits = True
  718.                         break
  719.                         continue
  720.                 
  721.                 if have_digits:
  722.                     modelid = word
  723.                     break
  724.                     continue
  725.             
  726.             digits = 0
  727.             digits_start = -1
  728.             digits_end = -1
  729.             for i in range(len(modelid)):
  730.                 if modelid[i].isdigit():
  731.                     if digits_start == -1:
  732.                         digits_start = i
  733.                     
  734.                     digits_end = i
  735.                     digits += 1
  736.                     continue
  737.                 if digits_start != -1:
  738.                     break
  739.                     continue
  740.             
  741.             digits_end += 1
  742.             modelnumber = 0
  743.             if digits > 0:
  744.                 modelnumber = int(modelid[digits_start:digits_end])
  745.             
  746.             modelpattern = modelid[:digits_start] + '%d' + modelid[digits_end:]
  747.             _debugprint("Searching for model ID '%s', '%s' %% %d" % (modelid, modelpattern, modelnumber))
  748.             ignore_digits = 0
  749.             best_mdl = None
  750.             found = False
  751.             while ignore_digits < digits:
  752.                 div = pow(10, ignore_digits)
  753.                 modelid = modelpattern % (modelnumber / div) * div
  754.                 _debugprint('Ignoring %d of %d digits, trying %s' % (ignore_digits, digits, modelid))
  755.                 for name, ppds in mdlitems:
  756.                     for word in name.split(' '):
  757.                         if word.lower() == modelid:
  758.                             found = True
  759.                             break
  760.                             continue
  761.                     
  762.                     if found:
  763.                         best_mdl = ppds.keys()
  764.                         break
  765.                         continue
  766.                 
  767.                 if found:
  768.                     break
  769.                 
  770.                 ignore_digits += 1
  771.                 if digits < 2:
  772.                     break
  773.                     continue
  774.             if found:
  775.                 ppdnamelist = best_mdl
  776.                 status = self.STATUS_MODEL_MISMATCH
  777.             
  778.         return (status, ppdnamelist)
  779.  
  780.     
  781.     def _getPPDNameFromCommandSet(self, commandsets = []):
  782.         '''Return ppd-name list or None, given a list of strings representing
  783.         the command sets supported.'''
  784.         
  785.         try:
  786.             self._init_makes()
  787.             models = self.makes['Generic']
  788.         except KeyError:
  789.             return None
  790.  
  791.         
  792.         def get(*candidates):
  793.             for model in candidates:
  794.                 (s, ppds) = self._findBestMatchPPDs(models, model)
  795.                 if s == self.STATUS_SUCCESS:
  796.                     return ppds
  797.             
  798.  
  799.         cmdsets = map((lambda x: x.lower()), commandsets)
  800.         if 'postscript' in cmdsets and 'postscript2' in cmdsets or 'postscript level 2 emulation' in cmdsets:
  801.             return get('PostScript Printer')
  802.         if 'pclxl' in cmdsets and 'pcl-xl' in cmdsets and 'pcl6' in cmdsets or 'pcl 6 emulation' in cmdsets:
  803.             return get('PCL 6/PCL XL Printer')
  804.         if 'pcl5e' in cmdsets:
  805.             return get('PCL 5e Printer')
  806.         if 'pcl5c' in cmdsets:
  807.             return get('PCL 5c Printer')
  808.         if 'pcl5' in cmdsets or 'pcl 5 emulation' in cmdsets:
  809.             return get('PCL 5 Printer')
  810.         if 'pcl' in cmdsets:
  811.             return get('PCL 3 Printer')
  812.         if 'escpl2' in cmdsets and 'esc/p2' in cmdsets or 'escp2e' in cmdsets:
  813.             return get('ESC/P Dot Matrix Printer')
  814.  
  815.     
  816.     def _init_makes(self):
  817.         if self.makes:
  818.             return None
  819.         makes = { }
  820.         lmakes = { }
  821.         lmodels = { }
  822.         for ppdname, ppddict in self.ppds.iteritems():
  823.             ppd_make_and_model = ppddict['ppd-make-and-model']
  824.             (make, model) = ppdMakeModelSplit(ppd_make_and_model)
  825.             lmake = make.lower()
  826.             lmodel = model.lower()
  827.             if not lmakes.has_key(lmake):
  828.                 lmakes[lmake] = make
  829.                 lmodels[lmake] = { }
  830.                 makes[make] = { }
  831.             else:
  832.                 make = lmakes[lmake]
  833.             if not lmodels[lmake].has_key(lmodel):
  834.                 lmodels[lmake][lmodel] = model
  835.                 makes[make][model] = { }
  836.             else:
  837.                 model = lmodels[lmake][lmodel]
  838.             makes[make][model][ppdname] = ppddict
  839.         
  840.         self.makes = makes
  841.         self.lmakes = lmakes
  842.         self.lmodels = lmodels
  843.  
  844.     
  845.     def _init_ids(self):
  846.         if self.ids:
  847.             return None
  848.         ids = { }
  849.         for ppdname, ppddict in self.ppds.iteritems():
  850.             if not ppddict.has_key('ppd-device-id'):
  851.                 continue
  852.             
  853.             id = ppddict['ppd-device-id']
  854.             if not id:
  855.                 continue
  856.             
  857.             v = id.find(':Model')
  858.             if v != -1:
  859.                 id = id[:v] + ';' + id[v + 1:]
  860.             
  861.             id_dict = parseDeviceID(id)
  862.             lmfg = id_dict['MFG'].lower()
  863.             lmdl = id_dict['MDL'].lower()
  864.             if lmfg == 'hewlett-packard':
  865.                 lmfg = 'hp'
  866.             
  867.             bad = False
  868.             if len(lmfg) == 0:
  869.                 _debugprint('Missing MFG field for %s' % ppdname)
  870.                 bad = True
  871.             
  872.             if len(lmdl) == 0:
  873.                 _debugprint('Missing MDL field for %s' % ppdname)
  874.                 bad = True
  875.             
  876.             if bad:
  877.                 continue
  878.             
  879.             if not ids.has_key(lmfg):
  880.                 ids[lmfg] = { }
  881.             
  882.             if not ids[lmfg].has_key(lmdl):
  883.                 ids[lmfg][lmdl] = []
  884.             
  885.             ids[lmfg][lmdl].append(ppdname)
  886.         
  887.         self.ids = ids
  888.  
  889.  
  890.  
  891. def _show_help():
  892.     print 'usage: ppds.py [--deviceid] [--list-models] [--list-ids] [--debug]'
  893.  
  894.  
  895. def _self_test(argv):
  896.     import sys
  897.     import getopt
  898.     
  899.     try:
  900.         (opts, args) = getopt.gnu_getopt(argv[1:], '', [
  901.             'help',
  902.             'deviceid',
  903.             'list-models',
  904.             'list-ids',
  905.             'debug'])
  906.     except getopt.GetoptError:
  907.         _show_help()
  908.         sys.exit(1)
  909.  
  910.     stdin_deviceid = False
  911.     list_models = False
  912.     list_ids = False
  913.     for opt, optarg in opts:
  914.         if opt == '--help':
  915.             show_help()
  916.             sys.exit(0)
  917.         
  918.         if opt == '--deviceid':
  919.             stdin_deviceid = True
  920.             continue
  921.         if opt == '--list-models':
  922.             list_models = True
  923.             continue
  924.         if opt == '--list-ids':
  925.             list_ids = True
  926.             continue
  927.     
  928.     picklefile = 'pickled-ppds'
  929.     import pickle
  930.     
  931.     try:
  932.         f = open(picklefile, 'r')
  933.         cupsppds = pickle.load(f)
  934.     except IOError:
  935.         f = open(picklefile, 'w')
  936.         c = cups.Connection()
  937.         cupsppds = c.getPPDs()
  938.         pickle.dump(cupsppds, f)
  939.  
  940.     ppds = PPDs(cupsppds)
  941.     makes = ppds.getMakes()
  942.     models_count = 0
  943.     for make in makes:
  944.         models = ppds.getModels(make)
  945.         models_count += len(models)
  946.         if list_models:
  947.             print make
  948.             for model in models:
  949.                 print '  ' + model
  950.             
  951.     
  952.     print '%d makes, %d models' % (len(makes), models_count)
  953.     ppds.getPPDNameFromDeviceID('HP', 'PSC 2200 Series')
  954.     makes = ppds.ids.keys()
  955.     models_count = 0
  956.     for make in makes:
  957.         models = ppds.ids[make]
  958.         models_count += len(models)
  959.         if list_ids:
  960.             print make
  961.             for model in models:
  962.                 print '  %s (%d)' % (model, len(ppds.ids[make][model]))
  963.                 for driver in ppds.ids[make][model]:
  964.                     print '    ' + driver
  965.                 
  966.             
  967.     
  968.     print '%d ID makes, %d ID models' % (len(makes), models_count)
  969.     print '\nID matching tests\n'
  970.     idlist = [
  971.         ('MFG:EPSON;CMD:ESCPL2,BDC,D4,D4PX;MDL:Stylus D78;CLS:PRINTER;DES:EPSON Stylus D78;', 1, 'Epson Stylus D68'),
  972.         ('MFG:Hewlett-Packard;MDL:LaserJet 1200 Series;CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;', 0, 'HP LaserJet 1200'),
  973.         ('MFG:Hewlett-Packard;MDL:LaserJet 3390 Series;CMD:MLC,PCL,POSTSCRIPT;CLS:PRINTER;', 0, 'HP LaserJet 3390'),
  974.         ('MFG:Hewlett-Packard;MDL:PSC 2200 Series;CMD:MLC,PCL,PML,DW-PCL,DYN;CLS:PRINTER;1284.4DL:4d,4e,1;', 0, 'HP PSC 2210'),
  975.         ('MFG:HP;MDL:PSC 2200 Series;CLS:PRINTER;DES:PSC 2200 Series;', 1, 'HP PSC 2210'),
  976.         ('MFG:HEWLETT-PACKARD;MDL:DESKJET 990C;CMD:MLC,PCL,PML;CLS:PRINTER;DES:Hewlett-Packard DeskJet 990C;', 0, 'HP DeskJet 990C'),
  977.         ('CLASS:PRINTER;MODEL:HP LaserJet 6MP;MANUFACTURER:Hewlett-Packard;DESCRIPTION:Hewlett-Packard LaserJet 6MP Printer;COMMAND SET:PJL,MLC,PCLXL,PCL,POSTSCRIPT;', 0, 'HP LaserJet 6MP'),
  978.         ('MFG:Canon;CMD:BJL,BJRaster3,BSCCe;SOJ:TXT01;MDL:iP3000;CLS:PRINTER;DES:Canon iP3000;VER:1.09;STA:10;FSI:03;', 1, 'Canon PIXMA iP3000'),
  979.         ('MFG:HP;MDL:Deskjet 5400 series;CMD:MLC,PCL,PML,DW-PCL,DESKJET,DYN;1284.4DL:4d,4e,1;CLS:PRINTER;DES:5440;', 1, 'HP DeskJet 5440'),
  980.         ('MFG:Hewlett-Packard;MDL:HP LaserJet 3390;CMD:PJL,MLC,PCL,POSTSCRIPT,PCLXL;', 0, 'HP LaserJet 3390.*Postscript'),
  981.         ('MFG:New;MDL:Unknown PS Printer;CMD:POSTSCRIPT;', 2, 'Generic postscript printer'),
  982.         ('MFG:New;MDL:Unknown PCL6 Printer;CMD:PCLXL;', 2, 'Generic PCL 6'),
  983.         ('MFG:New;MDL:Unknown PCL5e Printer;CMD:PCL5e;', 2, 'Generic PCL 5e'),
  984.         ('MFG:New;MDL:Unknown PCL5c Printer;CMD:PCL5c;', 2, 'Generic PCL 5c'),
  985.         ('MFG:New;MDL:Unknown PCL5 Printer;CMD:PCL5;', 2, 'Generic PCL 5'),
  986.         ('MFG:New;MDL:Unknown PCL3 Printer;CMD:PCL;', 2, 'Generic PCL'),
  987.         ('MFG:New;MDL:Unknown ESC/P Printer;CMD:ESCP2E;', 2, 'Generic ESC/P'),
  988.         ('MFG:New;MDL:Unknown Printer;', 100, None)]
  989.     if stdin_deviceid:
  990.         idlist = [
  991.             (raw_input('Device ID: '), 2, '')]
  992.     
  993.     all_passed = True
  994.     for id, max_status_code, modelre in idlist:
  995.         id_dict = parseDeviceID(id)
  996.         (status, ppdname) = ppds.getPPDNameFromDeviceID(id_dict['MFG'], id_dict['MDL'], id_dict['DES'], id_dict['CMD'])
  997.         if status < max_status_code:
  998.             success = True
  999.         elif status == max_status_code:
  1000.             ppddict = ppds.getInfoFromPPDName(ppdname)
  1001.             match = re.match(modelre, ppddict['ppd-make-and-model'], re.I)
  1002.             success = match != None
  1003.         else:
  1004.             success = False
  1005.         if success:
  1006.             result = 'PASS'
  1007.         else:
  1008.             result = '*** FAIL ***'
  1009.         print '%s: %s %s' % (result, id_dict['MFG'], id_dict['MDL'])
  1010.         if all_passed:
  1011.             pass
  1012.         all_passed = success
  1013.     
  1014.     if not all_passed:
  1015.         raise RuntimeError
  1016.     all_passed
  1017.  
  1018.